package com.chwl.framework.springboot.grpc.service;

import com.chwl.framework.grpc.service.GrpcRequest;
import com.chwl.framework.grpc.service.GrpcResponse;
import com.chwl.framework.grpc.utils.ProtobufUtils;
import com.chwl.framework.rpc.CommonServiceGrpc;
import com.chwl.framework.rpc.GrpcService;
import com.google.protobuf.ByteString;
import io.grpc.stub.StreamObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cglib.reflect.FastClass;
import org.springframework.cglib.reflect.FastMethod;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.stereotype.Service;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class CommonService extends CommonServiceGrpc.CommonServiceImplBase {

    private static final Logger log = LoggerFactory.getLogger(CommonService.class.getName());

    private Map<String, Object> serviceBeanMap = new ConcurrentHashMap<>();

    private final AbstractApplicationContext applicationContext;

    public CommonService(AbstractApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public void handle(GrpcService.Request request, StreamObserver<GrpcService.Response> responseObserver) {
        GrpcRequest grpcRequest = ProtobufUtils.deserialize(request.getRequest().toByteArray(), GrpcRequest.class);
        GrpcResponse response = new GrpcResponse();
        String beanName = grpcRequest.getBeanName();
        Object bean = getBean(beanName);
        if (bean == null) {
            response.error("Service bean '" + beanName + "' not found.");
            build(responseObserver, response);
            return;
        }

        String methodName = grpcRequest.getMethodName();
        Object[] args = grpcRequest.getArgs();
        FastClass serviceFastClass = FastClass.create(bean.getClass());
        Class<?>[] argTypes = getMethodParameterTypes(args);
        FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, argTypes);
        try {
            Object result = serviceFastMethod.invoke(bean, args);
            response.success(result);
            build(responseObserver, response);
        } catch (InvocationTargetException e) {
            log.error("common service,handle fail,error message:{}", e.getMessage(), e);
            response.error("InvocationTargetException : " + e.getMessage());
            build(responseObserver, response);
        }
    }

    public void build(StreamObserver<GrpcService.Response> responseObserver, GrpcResponse response) {
        ByteString bytes = ByteString.copyFrom(ProtobufUtils.serialize(response));
        GrpcService.Response grpcResponse = GrpcService.Response.newBuilder().setReponse(bytes).build();
        responseObserver.onNext(grpcResponse);
        responseObserver.onCompleted();
    }

    /**
     * 获取 Service Bean
     */
    private Object getBean(String beanName) throws NoSuchBeanDefinitionException {
        if (serviceBeanMap.containsKey(beanName)) {
            return serviceBeanMap.get(beanName);
        }
        try {
            Object bean = applicationContext.getBean(beanName);
            //未找到通过前缀匹配
            if (bean.toString().indexOf("NullPointerException") >= 0) {
                throw new Exception("not found");
            }
            serviceBeanMap.put(beanName, bean);
            return bean;
        } catch (Exception e) {
            // 通过前缀匹配 Bean
            String[] serviceBeanNames = applicationContext.getBeanNamesForAnnotation(Service.class);
            for (String serviceBeanName : serviceBeanNames) {
                if (serviceBeanName.contains(beanName)) {
                    Object bean = applicationContext.getBean(serviceBeanName);
                    serviceBeanMap.put(beanName, bean);
                    return bean;
                }
            }
        }
        return null;
    }

    /**
     * 获取对象数组的 Class 类型
     */
    private Class<?>[] getMethodParameterTypes(Object[] args) {
        if (args == null) {
            return null;
        }
        Class<?>[] types = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            Class<?> type = args[i].getClass();
            types[i] = type;
        }
        return types;
    }
}
